package com.gitee.c0dehvb.reactive.sample.r2dbc.connection;

import com.gitee.c0dehvb.reactive.sample.r2dbc.ShardingRewriteEngine;
import io.r2dbc.spi.Connection;
import io.r2dbc.spi.Result;
import io.r2dbc.spi.Statement;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;

import java.util.ArrayList;
import java.util.List;

/**
 * 分片封装Statement
 * @author LiYilin
 * @since 2022/6/16 11:07 AM
 **/
@Slf4j
public class ShardingStatement implements Statement {
    private final String sql;
    private final List<Object> parameters;
    private final Connection connection;
    private final ShardingRewriteEngine shardingRewriteEngine;

    private final List<Statement> statements;

    public ShardingStatement(String sql, Connection connection, ShardingRewriteEngine shardingRewriteEngine) {
        this.sql = sql;
        this.connection = connection;
        this.shardingRewriteEngine = shardingRewriteEngine;

        this.parameters = new ArrayList<>(16);
        this.statements = new ArrayList<>(16);
    }

    @Override
    public Statement add() {
        statements.add(createInternalStatement());
        return this;
    }

    @Override
    public Publisher<? extends Result> execute() {
        statements.add(createInternalStatement());
        // 顺序执行Statements
        return Flux.fromIterable(statements)
                .flatMap(Statement::execute);
    }

    @Override
    public Statement bind(int index, Object value) {
        setParameter(index, value);
        return this;
    }

    @Override
    public Statement bind(String name, Object value) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Statement bindNull(int index, Class<?> type) {
        setParameter(index, null);
        return this;
    }

    @Override
    public Statement bindNull(String name, Class<?> type) {
        throw new UnsupportedOperationException();
    }

    private void setParameter(final int parameterIndex, final Object value) {
        if (parameters.size() == parameterIndex) {
            parameters.add(value);
            return;
        }
        for (int i = parameters.size(); i <= parameterIndex; i++) {
            parameters.add(null);
        }
        parameters.set(parameterIndex, value);
    }

    private Statement createInternalStatement() {
        String actualSQL = resolveSql(sql, parameters);
        log.info("create statement, LogicSQL: {}\nActualSQL: {}", sql, actualSQL);
        Statement statement = connection.createStatement(actualSQL);
        commitParameters(statement);
        return statement;
    }

    /**
     * 转换Logic SQL为Actual SQL
     * @param sql
     * @return
     */
    protected String resolveSql(String sql, List<Object> parameters) {
        return shardingRewriteEngine.rewriteSql(sql, parameters);
    }

    private void commitParameters(Statement statement) {
        if (!parameters.isEmpty()) {
            int idx = 0;
            parameters.forEach(value -> {
                if (value == null) {
                    statement.bindNull(idx, Object.class);
                } else {
                    statement.bind(idx, value);
                }
            });
            parameters.clear();
        }
    }

    // @Data
    // @NoArgsConstructor
    // @AllArgsConstructor
    // private static class BindParam {
    //     private boolean bindNull;
    //     private Class<?> valueType;
    //     private Object value;
    //
    //     static BindParam bindNull(Class<?> valueType) {
    //         return new BindParam(true, valueType, null);
    //     }
    //     static BindParam bindValue(Object value) {
    //         return new BindParam(false, null, value);
    //     }
    // }
}
